/**
	Pulsar engine. Core.
	Memory monitror and statistics

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#include <pulsar/pulsar.h>
#include <pulsar/src/pulsar_intr.h>

namespace pl
{

pl::List<IMemoryMonitor> IMemoryMonitor::_memMonitorGroup;

} // namespace pl

pl::IMemoryMonitor::IMemoryMonitor() :
pl::ListNode<IMemoryMonitor>(_memMonitorGroup)
{
}

/**
	Forward allocation event to all subscribers
*/
void pl::IMemoryMonitor::_callOnAlloc(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func, pl::BlockType type)
{
	pl::ListEnumerator<IMemoryMonitor> enumForw(_memMonitorGroup);

	while (!enumForw.isFinished())
	{
		enumForw->onAlloc(p, size, file, line, func, type);
	}
	return;
}

/**
	Forward free event to all subscribers
*/
void pl::IMemoryMonitor::_callOnFree(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func, BlockType type)
{
	pl::ListEnumerator<IMemoryMonitor> enumForw(_memMonitorGroup);

	while (!enumForw.isFinished())
	{
		enumForw->onFree(p, size, file, line, func, type);
	}
	return;
}

/**
	Dump all memory events to file
*/
pl::MemoryMonitorFile::MemoryMonitorFile(const pl::Filepath& monitorFile) :
_curPtr(0),
_prevPointerInfo(NULL),
_prevSizeInfo(0),
_prevLineNum(0)
{
	_pStream = pl::FileManager::openFileWrite(monitorFile, pl::OPEN_FILE_CREATEALWAYS);
	_write(PL_FOURCC('M', 'E', 'M', 'M'));
	_write(pl::getTimeMS());
	return;
}

/**
*/
pl::MemoryMonitorFile::~MemoryMonitorFile()
{
	_flush();
	PL_DELETE _pStream;
	return;
}

void pl::MemoryMonitorFile::onAlloc(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func, BlockType type)
{
	MemoryMonitorHdr hdr = {0};
	pl::Lock locker(&_memCs);

	hdr.eventCode = type;
	hdr.deleteFlag = false;
	_saveInfo(hdr, p, size, file, line, func);
	return;
}

void pl::MemoryMonitorFile::onFree(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func, BlockType type)
{
	MemoryMonitorHdr hdr = {0};
	pl::Lock locker(&_memCs);

	hdr.eventCode = type;
	hdr.deleteFlag = true;
	_saveInfo(hdr, p, 0, file, line, func);
	return;
}

void pl::MemoryMonitorFile::pushEvent(const pl_char* evt)
{
	MemoryMonitorHdr hdr = {0};
	pl::Lock locker(&_memCs);

	hdr.eventCode = BLOCK_TYPE_EVENT;
	hdr.textPresent = 1;

	_write(hdr);
	_write(pl::getTimeMS());
	_writeStringAnsi(evt);
	return;
}

void pl::MemoryMonitorFile::_saveInfo(MemoryMonitorHdr& hdr, void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func)
{
	pl::Filepath shortName;

	if (p != NULL)
	{
		if (_prevPointerInfo == p)
		{
			hdr.pointerPresent = 0x2; // The same as previous
		}
		else
		{
			hdr.pointerPresent = 0x1; // Specified
			_prevPointerInfo = p;
		}
	}
	if (size != 0)
	{
		if (size == _prevSizeInfo)
		{
			hdr.sizePresent = 0x2; // The same as previous
		}
		else
		{
			hdr.sizePresent = 0x1; // Specified
			_prevSizeInfo = (pl_uint32)size;
		}
	}
	if (file != NULL)
	{
		shortName = extractFilename(file);
		if (shortName == _prevFileName)
		{
			hdr.fileNamePresent = 0x2; // The same as previous
		}
		else
		{
			_prevFileName = shortName;
			hdr.fileNamePresent = 0x1; // Specified
		}
	}
	if (line != 0)
	{
		if (_prevLineNum == line)
		{
			hdr.lineNmbPresent = 0x2; // The same as previous
		}
		else
		{
			_prevLineNum = line;
			hdr.lineNmbPresent = 0x1; // Specified
		}
	}

	_write(hdr);
	_write(pl::getTimeMS());

	if (hdr.pointerPresent == 0x1)
	{
		_write(p);
	}
	if (hdr.sizePresent == 0x1)
	{
		_write((pl_uint32)size);
	}
	if (hdr.fileNamePresent == 0x1)
	{
		_writeStringAnsi(shortName.get());
	}
	if (hdr.lineNmbPresent == 0x1)
	{
		_write(line);
	}
	return;
}

void pl::MemoryMonitorFile::_write(const void* pBuf, pl_int32 itemSize)
{
	if (_pStream != NULL && pBuf != NULL)
	{
		if (_curPtr + itemSize > sizeof(_cacheArea))
		{
			_flush();
		}
		memcpy(_cacheArea + _curPtr, pBuf, itemSize);
		_curPtr += itemSize;
	}
	return;
}
void pl::MemoryMonitorFile::_writeStringAnsi(const pl_char* str)
{
	do
	{
		_write((pl_uint8)(*str));
	}
	while (*(str++) != PL_T('\0'));
	return;
}

void pl::MemoryMonitorFile::_flush()
{
	if (_pStream != NULL)
	{
		_pStream->writeBuffer(_cacheArea, 1, _curPtr);
		_curPtr = 0;
	}
	return;
}

/**
	Save all memory statistics
*/
pl::MemoryMonitorStat::MemoryMonitorStat()
{
	memset(&_stat, 0, sizeof(_stat));
	return;
}

void pl::MemoryMonitorStat::onAlloc(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func, BlockType type)
{
	pl::Lock l(&_cs);

	_stat.nAllocCurrentBlocks++;
	_stat.nAllocSumBlocks++;
	_stat.nAllocSize += size;
	_stat.nSumAllocSize += size;

	if (size > 0 && size < SMALL_BLOCK_SIZE_MAX)
	{
		_stat.nSmallAllocations[size]++;
	}
	_adjustPeaks();
	_adjustMinMax(p);
	return;
}

void pl::MemoryMonitorStat::onFree(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func, BlockType type)
{
	pl::Lock l(&_cs);

	_stat.nAllocCurrentBlocks--;
	_stat.nFreeBlocks++;
	_stat.nAllocSize -= size;
	return;
}

/**
*/
void pl::MemoryMonitorStat::_adjustMinMax(void* p)
{
	if (p != NULL)
	{
		if (_stat.allocatedPtrMin == NULL)
		{
			_stat.allocatedPtrMin = p;
		}
		else
		{
			_stat.allocatedPtrMin = pl::min(p, _stat.allocatedPtrMin);
		}
		if (_stat.allocatedPtrMax == NULL)
		{
			_stat.allocatedPtrMax = p;
		}
		else
		{
			_stat.allocatedPtrMax = pl::max(p, _stat.allocatedPtrMax);
		}
	}
	return;
}

/**
*/
void pl::MemoryMonitorStat::_adjustPeaks()
{
	if (_stat.peakAllocationSize < _stat.nAllocSize)
	{
		_stat.peakAllocationSize = _stat.nAllocSize;
		_stat.peakAllocationSizeIndex = _memGlobalInfo.curAllocationIndex;
	}
	if (_stat.peakAllocationCount < _stat.nAllocCurrentBlocks)
	{
		_stat.peakAllocationCount = _stat.nAllocCurrentBlocks;
		_stat.peakAllocationCountIndex = _memGlobalInfo.curAllocationIndex;
	}
	return;
}

//
// End of file 'pulsar_memory_monitor.cpp'
//
